/*-*
 * FILENAME  :
 *    $HeadURL$
 *
 * STATUS  :
 *    $Revision$
 *
 *    $Author$
 *    $Date$
 *  
 *    
 * Copyright (c) 2011 Gonalge Ltda. All rights reserved.
 *
 ****************************************************************/

package org.gonalge.sudoku.negocio;

import java.io.Serializable;
import java.util.Random;

import org.gonalge.sudoku.dto.Celda;

/**
 * 
 * @author Geremias Gonzalez
 */
public class Sudoku implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1797400404329286175L;

    private Matriz tabla;

    private int nivel;

    private int nroVueltas;

    private Lista aleatorioCell = new Lista();

    // ******auxiliares********
    private Matriz aux = new Matriz();

    private int filaSol = 0;

    private int columnaSol = 0;

    private int eleSol = 0;

    // ************************

    public Sudoku() {
        tabla = new Matriz();
        nroVueltas = 0;
        eleSol = tabla.getColumna();
        nivel = (6 * tabla.getDimCuadrante() - tabla.getDimCuadrante()) / 2;
    }

    public Sudoku(int c) {
        tabla = new Matriz(c, c);
        aux = new Matriz(c, c);
        eleSol = tabla.getColumna();
        nroVueltas = 0;
        nivel = (6 * tabla.getDimCuadrante() - tabla.getDimCuadrante()) / 2;
    }

    private boolean backTrack(Matriz Dato) {

        if (Terminacion(Dato)) {
            return true;
        }

        Lista ListaReglas = reglasAplicables(Dato);

        while (!ListaReglas.isEmpty()) {
            nroVueltas++;
            Celda r = elegirMejorRegla(Dato, ListaReglas);
            aplicarRegla(r, Dato);

            if (backTrack(Dato))
                return true;
            else
                desaplicarRegla(r, Dato);

        }
        return false;
    }

    public void llenadoAleatorio() {

        eleSol = tabla.getColumna();
        columnaSol = filaSol = 0;
        BacktrackSolucion();
        Random a = new Random();

        aleatorioCell = new Lista();
        for (int i = 0; i < nivel; i++) {
            int f = a.nextInt(tabla.getFila());
            int c = a.nextInt(tabla.getColumna());
            Celda cel = new Celda(f, c, tabla.Consultar(f, c));
            aleatorioCell.addicionarEle(cel);
        }

        vaciarTablero();

        int i = 0;
        while (i < aleatorioCell.getSize()) {
            Celda ce = (Celda) aleatorioCell.getEle(i);
            tabla.insertar(ce.getFila(), ce.getColumna(), ce.getEle());
            i++;
        }
        aux = trasferir(aux, tabla);
    }

    public boolean BacktrackSolucion() {
        nroVueltas = 0;
        tabla = trasferir(tabla, aux);

        if (tabla.Consultar(filaSol, columnaSol) == 0)
            tabla.insertar(filaSol, columnaSol, eleSol);

        return backTrack(tabla);
    }

    private Matriz trasferir(Matriz res, Matriz a) {

        for (int i = 0; i < tabla.getFila(); i++)
            for (int j = 0; j < tabla.getColumna(); j++)
                res.insertar(i, j, a.Consultar(i, j));
        return res;
    }

    public void desaplicarRegla(Celda r, Matriz dato) {
        dato.limpiar(r.getFila(), r.getColumna());
    }

    private Lista ListaPosibilidades(Matriz dato, int f, int c) {

        Lista a = new Lista();
        for (int i = 1; i <= dato.getColumna(); i++) {
            if (dato.verificarColumna(c, i) && dato.verificarFila(f, i) && dato.verificarSubMatriz(f, c, i)) {
                a.addicionarEle(new Celda(f, c, i));
            }
        }
        return a;
    }

    private Lista reglasAplicables(Matriz Dato) {

        Lista aux = new Lista();
        aux.setSize(tabla.getColumna() + 5);

        for (int i = 0; i < Dato.getFila(); i++)
            for (int j = 0; j < Dato.getColumna(); j++)
                if (Dato.Consultar(i, j) == 0) {
                    Lista a = ListaPosibilidades(Dato, i, j);
                    if (aux.getSize() > a.getSize())
                        aux = a;
                }
        return aux;
    }

    /**
     * 
     * La-euristica-esta-en-cargar-las-reglas-aplicables
     * 
     * @param dato
     * @param reglas
     * @return
     */
    private Celda elegirMejorRegla(Matriz dato, Lista reglas) {
        return (Celda) reglas.getEleNext();
    }

    private void aplicarRegla(Celda c, Matriz dato) {
        dato.insertar(c.getFila(), c.getColumna(), c.getEle());
    }

    public Matriz getTabla() {
        return tabla;
    }

    private boolean Terminacion(Matriz m) {

        for (int i = 0; i < m.getFila(); i++) {
            for (int j = 0; j < m.getColumna(); j++)
                if (m.Consultar(i, j) == 0)
                    return false;
        }
        return true;
    }

    public void vaciarTablero() {
        for (byte i = 0; i < tabla.getFila(); i++)
            for (byte j = 0; j < tabla.getColumna(); j++)
                tabla.limpiar(i, j);
        nroVueltas = 0;

    }

    public int getNroVueltas() {
        return nroVueltas;
    }

    public void setNroVueltas(int nroVueltas) {
        this.nroVueltas = nroVueltas;
    }

    public Lista Posibilidades(int i, int j) {
        if (tabla.Consultar(i, j) != 0)
            return new Lista();
        return ListaPosibilidades(tabla, i, j);
    }

    public void setNivel(int nivel) {
        this.nivel = nivel;
    }

    public Lista getAleatoCellS() {
        return aleatorioCell;
    }

    public int getNivel() {
        return nivel;
    }

    public Matriz getAux() {
        return aux;
    }

    public void setTabla(Matriz tabla) {
        vaciarTablero();
        this.tabla = trasferir(tabla, tabla);
    }

    public void acualizarFilColEleSol(boolean sw) {

        if (sw) {
            if (eleSol == 0 || tabla.Consultar(filaSol, columnaSol) != 0) {
                if (columnaSol < tabla.getColumna() - 1) {
                    columnaSol++;
                    eleSol = tabla.getColumna();
                } else {
                    if (filaSol < tabla.getFila() - 1) {
                        filaSol++;
                        columnaSol = 0;
                        eleSol = tabla.getColumna();
                    }
                }
            } else
                eleSol--;
        } else {
            if (eleSol == tabla.getColumna() || tabla.Consultar(filaSol, columnaSol) != 0) {
                if (columnaSol > 0) {
                    columnaSol--;
                    eleSol = 0;
                } else {
                    if (filaSol > 0) {
                        filaSol--;
                        columnaSol = 0;
                        eleSol = 0;
                    }
                }
            } else
                eleSol++;

        }
    }
}
